package com.franklin.ideaplugin.easytesting.core.rpc;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.franklin.ideaplugin.easytesting.common.cache.EasyTestingCache;
import com.franklin.ideaplugin.easytesting.common.entity.ETRsp;
import com.franklin.ideaplugin.easytesting.common.log.ILogger;
import com.franklin.ideaplugin.easytesting.common.log.LoggerFactory;
import com.franklin.ideaplugin.easytesting.common.utils.ExceptionUtils;
import com.franklin.ideaplugin.easytesting.common.utils.JsonUtils;
import com.franklin.ideaplugin.easytesting.core.thread.EasyTestingThreadPool;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Ye Junhui
 * @since 2023/5/15
 */
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private static final ILogger log = LoggerFactory.getLogger(NettyHttpServerHandler.class);

    private final Map<String, INettyHttpRequestHandler> requestHandlerMap;

    public NettyHttpServerHandler(List<INettyHttpRequestHandler> nettyHttpRequestHandlerList) {
        this.requestHandlerMap = nettyHttpRequestHandlerList.stream()
                .collect(Collectors.toConcurrentMap(INettyHttpRequestHandler::getRequestUrl, Function.identity()));
    }

    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        // request parse
        //final byte[] requestBytes = ByteBufUtil.getBytes(msg.content());    // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8);
        String requestData = msg.content().toString(CharsetUtil.UTF_8);
        String uri = msg.uri();
        HttpMethod httpMethod = msg.method();
        boolean keepAlive = HttpUtil.isKeepAlive(msg);

        // invoke
        EasyTestingThreadPool.getBizThreadPool().execute(() -> {
            // do invoke
            Object responseObj = process(httpMethod, uri, requestData);

            // to json
            String responseJson = JsonUtils.toJSONString(responseObj);

            // write response
            writeResponse(ctx, keepAlive, responseJson);
        });
    }

    private ETRsp<?> process(HttpMethod httpMethod, String uri, String requestData) {
        if (HttpMethod.POST != httpMethod) {
            return ETRsp.fail("invalid request, HttpMethod not support.");
        }
        if (StrUtil.isBlank(uri)) {
            return ETRsp.fail("invalid request, uri-mapping empty.");
        }

        //映射
        INettyHttpRequestHandler nettyHttpRequestHandler = this.requestHandlerMap.get(uri);
        if (Objects.nonNull(nettyHttpRequestHandler)) {
            Object input = JsonUtils.parseObject(requestData, nettyHttpRequestHandler.getInputType());
            String traceId = IdUtil.fastSimpleUUID();
            EasyTestingCache.setTraceId(traceId);
            try {
                Object output = nettyHttpRequestHandler.handleRequest(input);
                if (output instanceof ETRsp){
                    ETRsp<?> etRsp = (ETRsp<?>) output;
                    return etRsp;
                }
                return ETRsp.success(output);
            } catch (Throwable e) {
                log.error("method invoke fail", e);
                return ETRsp.fail(ExceptionUtils.exceptionToString(e));
            }finally {
                EasyTestingCache.clear(traceId);
            }
        }
        return ETRsp.fail("invalid request, uri-mapping fail.");
    }

    /**
     * write response
     */
    private void writeResponse(ChannelHandlerContext ctx, boolean keepAlive, String responseJson) {
        // write response
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(responseJson, CharsetUtil.UTF_8));   //  Unpooled.wrappedBuffer(responseJson)
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json;charset=UTF-8");       // HttpHeaderValues.TEXT_PLAIN.toString()
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        if (keepAlive) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        ctx.writeAndFlush(response);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error(">>>>>>>>>>> easy-testing provider netty_http server caught exception", cause);
        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            ctx.channel().close();      // beat 3N, close if idle
            log.debug(">>>>>>>>>>> easy-testing provider netty_http server close an idle channel.");
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}
